package moon.core.instruction;

import java.util.ArrayList;
import java.util.List;

import moon.core.ObjectMoon;
import moon.core.classdef.ClassDef;
import moon.core.classdef.MethDec;

/** 
 * Code fragment contained in a class. Usually a method body.
 * 
 * @see moon.core.ObjectMoon
 * @see moon.core.classdef.ClassDef
 * @see moon.core.classdef.MethDec
 * 
 * @author <a href="mailto:rmartico@ubu.es">Ra&uacute;l Marticorena</a>
 * @since MOON-2.3.0
 */
@SuppressWarnings("serial")
public abstract class CodeFragment extends ObjectMoon {

	/**
	 * Class.
	 */
	private ClassDef classDef;
	
	/**
	 * End line.
	 */
	private int endLine;

	/**
	 * End column.
	 */
	private int endColumn;
	
	/**
	 * Text.
	 */
	private String text;

	/**
	 * Constructor of code fragment.
	 * 
	 * @param beginLine begin line
	 * @param beginColumn begin column
	 * @param classDef class
	 * @param text text
	 */
	public CodeFragment(int beginLine, int beginColumn, ClassDef classDef, String text) {
		super(beginLine, beginColumn);
		this.classDef = classDef; 
		this.setText(text);
		this.setEndLine(0); // UNKNOWN
		this.setEndColumn(0); // UNKNOWN
	}

	/**
	 * Constructor of code fragment.
	 * 
	 * @param beginLine begin line
	 * @param beginColumn begin column 
	 * @param endLine end line
	 * @param endColumn end column 
	 * @param classDef container class
	 */
	public CodeFragment(int beginLine, int beginColumn,
						int endLine, int endColumn, ClassDef classDef){
		super(beginLine,beginColumn);
		this.setEndLine(endLine);
		this.setEndColumn(endColumn);
		this.classDef = classDef;
	}
	
	/**
	 * Constructor of code fragment.
	 * 
	 * @param beginLine begin line
	 * @param beginColumn begin column 
	 * @param endLine end line
	 * @param endColumn end column 
	 * @param classDef container class
	 * @param text text
	 */
	public CodeFragment(int beginLine, int beginColumn,
						int endLine, int endColumn, ClassDef classDef, String text){
		super(beginLine,beginColumn);
		this.setEndLine(endLine);
		this.setEndColumn(endColumn);
		this.classDef = classDef;
		this.setText(text);
	}
	
	/**
	 * Gets the container class.
	 * 
	 * @return class
	 */
	public ClassDef getClassDef(){
		return this.classDef;
	}
	
	/**
	 * Gets the method that contains the lines.
	 * 
	 * @return method or null
	 */
	public MethDec getMethDec(){
		List<MethDec> methods = this.classDef.getMethDec();
		
		for (MethDec md : methods){
			long beginLineMethod = md.getLine();
			List<Instr> instructions = md.getInstructions();
			long endLineMethod = beginLineMethod + lastLineMethod(instructions);
			if (this.getLine() >= beginLineMethod &&
					this.getEndLine() <= endLineMethod){
				return md;
			}
		}
		
		return null;
	}

	/**
	 * Sets the end line.
	 * 
	 * @param endLine end line
	 * @see #getEndLine
	 */
	public void setEndLine(int endLine) {
		this.endLine = endLine;
	}

	/**
	 * Get the end line.
	 * 
	 * @return end line
	 * @see #setEndLine
	 */
	public int getEndLine() {
		return endLine;
	}

	/**
	 * Sets the end column.
	 *
	 * @param endColumn end column
     * @see #getEndColumn
	 */
	public void setEndColumn(int endColumn) {
		this.endColumn = endColumn;
	}

	/**
	 * Gets the end column.
	 * 
	 * @return end column
	 * @see #setEndColumn
	 */
	public int getEndColumn() {
		return endColumn;
	}

	/**
	 * Sets the text.
	 * 
	 * @param text text
	 */
	private void setText(String text) {
		this.text = text;
	}

	/**
	 * Gets the text.
	 * 
	 * @return text
	 */
	public String getText() {
		return text;
	}
	
	/**
	 * Gets the last line of a set of instructions.
	 * 
	 * @param instructions
	 * @return last line
	 */
	private long lastLineMethod(List<Instr> instructions){
		long lastLine = 0;
		for (Instr instr : instructions){
			lastLine = getLastLine(instr, lastLine);			
		}
		return lastLine;
	}
	
	/**
	 * Last line.
	 * 
	 * @param instr instr
	 * @param lastLine last line
	 * @return max last line
	 */
	private long getLastLine(Instr instr, long lastLine){
		if (instr instanceof CompoundInstr){
			List<Instr> list = ((CompoundInstr) instr).getInstructions();
			for (Instr instrInCompound : list){
				if (lastLine < getLastLine(instrInCompound, lastLine)){
					lastLine = getLastLine(instrInCompound, lastLine);
				}
			}
			return lastLine;
		}
		else if (instr.getLine() > lastLine){
			return instr.getLine();
		}
		return lastLine;
	}
	
	/**
	 * Gets the instructions of this code fragment if its is in a method body,
	 * loosing the information of blocks.
	 * 
	 * @return instructions or null
	 */
	public List<Instr> getFlattenedInstructionsInMethod(){		
		if (this.getMethDec()!=null){
			long baseLine = this.getMethDec().getLine();
			List<Instr> instructions = this.getMethDec().getInstructions();
			List<Instr> fragment = new ArrayList<Instr>();
			for (Instr instr : instructions){				
				visit(instr, fragment, baseLine);
			}	
			return fragment;
		}
		else{
			return null;
		}
	}

	/**
	 * Gets the instructions recursively.
	 * 
	 * @param instr instr
	 * @param fragment fragment
	 * @param baseLine base line of method
	 */
	private void visit(Instr instr, List<Instr> fragment, long baseLine){
		if (instr instanceof CompoundInstr){
			List<Instr> compound = ((CompoundInstr) instr).getInstructions();
			for (Instr instrCompound : compound){
				visit(instrCompound, fragment, baseLine);
			}
		}
		else {
			if ((instr.getLine() + baseLine >=this.getLine() &&
			instr.getLine() + baseLine <=this.getEndLine())){
				fragment.add(instr);
			}
		}
	}
	
	
	/**
	 * Gets the instructions of this code fragment if its is in a method body.
	 *  
	 * @return instructions or null
	 */
	public List<Instr> getInstructionsInMethod(){
		if (this.getMethDec()!=null){

			long baseLine = this.getMethDec().getLine();
			List<Instr> instructions = this.getMethDec().getInstructions();
			// first instructions is a compound instruction...
			if (instructions.get(0) instanceof CompoundInstr){
				instructions = ((CompoundInstr) instructions.get(0)).getInstructions();
			}
			List<Instr> fragment = new ArrayList<Instr>();
			for (Instr instr : instructions) {
				if (instr instanceof CompoundInstr) {
					CompoundInstr compoundInstr = (CompoundInstr) instr;
					if (compoundInstr.getFirstInstr().getLine() + baseLine >= this
							.getLine()
							&& compoundInstr.getLastInstr().getLine()
									+ baseLine <= this.getEndLine()) {
						fragment.add(instr);
					}
				} else {
					if ((instr.getLine() + baseLine >= this.getLine() && instr
							.getLine()
							+ baseLine <= this.getEndLine())) {
						fragment.add(instr);
					}
				}
			}	
			return fragment;
		}
		else{
			return null;
		}
	}
}
